# typed: true # rubocop:disable Sorbet/StrictSigil
# frozen_string_literal: true

require "macho"

module OS
  module Mac
    module Hardware
      module CPU
        module ClassMethods
          extend T::Helpers

          # These methods use info spewed out by sysctl.
          # Look in <mach/machine.h> for decoding info.
          def type
            case ::Hardware::CPU.sysctl_int("hw.cputype")
            when MachO::Headers::CPU_TYPE_I386
              :intel
            when MachO::Headers::CPU_TYPE_ARM64
              :arm
            else
              :dunno
            end
          end

          def family
            if ::Hardware::CPU.arm?
              ::Hardware::CPU.arm_family
            elsif ::Hardware::CPU.intel?
              ::Hardware::CPU.intel_family
            else
              :dunno
            end
          end

          # True when running under an Intel-based shell via Rosetta 2 on an
          # Apple Silicon Mac. This can be detected via seeing if there's a
          # conflict between what `uname` reports and the underlying `sysctl` flags,
          # since the `sysctl` flags don't change behaviour under Rosetta 2.
          def in_rosetta2?
            ::Hardware::CPU.sysctl_bool!("sysctl.proc_translated")
          end

          def features
            @features ||= ::Hardware::CPU.sysctl_n(
              "machdep.cpu.features",
              "machdep.cpu.extfeatures",
              "machdep.cpu.leaf7_features",
            ).split.map { |s| s.downcase.to_sym }
          end

          def sse4?
            ::Hardware::CPU.sysctl_bool!("hw.optional.sse4_1")
          end
        end
      end
    end
  end
end

Hardware::CPU.singleton_class.prepend(OS::Mac::Hardware::CPU::ClassMethods)

module Hardware
  class CPU
    class << self
      def extmodel
        sysctl_int("machdep.cpu.extmodel")
      end

      def aes?
        sysctl_bool!("hw.optional.aes")
      end

      def altivec?
        sysctl_bool!("hw.optional.altivec")
      end

      def avx?
        sysctl_bool!("hw.optional.avx1_0")
      end

      def avx2?
        sysctl_bool!("hw.optional.avx2_0")
      end

      def sse3?
        sysctl_bool!("hw.optional.sse3")
      end

      def ssse3?
        sysctl_bool!("hw.optional.supplementalsse3")
      end

      def sse4_2?
        sysctl_bool!("hw.optional.sse4_2")
      end

      # NOTE: This is more reliable than checking `uname`. `sysctl` returns
      #       the right answer even when running in Rosetta 2.
      def physical_cpu_arm64?
        sysctl_bool!("hw.optional.arm64")
      end

      def virtualized?
        sysctl_bool!("kern.hv_vmm_present")
      end

      def arm_family
        case sysctl_int("hw.cpufamily")
        when 0x2c91a47e             # ARMv8.0-A (Typhoon)
          :arm_typhoon
        when 0x92fb37c8             # ARMv8.0-A (Twister)
          :arm_twister
        when 0x67ceee93             # ARMv8.1-A (Hurricane, Zephyr)
          :arm_hurricane_zephyr
        when 0xe81e7ef6             # ARMv8.2-A (Monsoon, Mistral)
          :arm_monsoon_mistral
        when 0x07d34b9f             # ARMv8.3-A (Vortex, Tempest)
          :arm_vortex_tempest
        when 0x462504d2             # ARMv8.4-A (Lightning, Thunder)
          :arm_lightning_thunder
        when 0x573b5eec, 0x1b588bb3 # ARMv8.4-A (Firestorm, Icestorm)
          :arm_firestorm_icestorm
        when 0xda33d83d             # ARMv8.5-A (Blizzard, Avalanche)
          :arm_blizzard_avalanche
        when 0xfa33415e             # ARMv8.6-A (M3, Ibiza)
          :arm_ibiza
        when 0x5f4dea93             # ARMv8.6-A (M3 Pro, Lobos)
          :arm_lobos
        when 0x72015832             # ARMv8.6-A (M3 Max, Palma)
          :arm_palma
        when 0x6f5129ac             # ARMv9.2-A (M4, Donan)
          :arm_donan
        when 0x17d5b93a             # ARMv9.2-A (M4 Pro / M4 Max, Brava)
          :arm_brava
        else
          # When adding new ARM CPU families, please also update
          # test/hardware/cpu_spec.rb to include the new families.
          :dunno
        end
      end

      def intel_family(_family = nil, _cpu_model = nil)
        case sysctl_int("hw.cpufamily")
        when 0x73d67300 # Yonah: Core Solo/Duo
          :core
        when 0x426f69ef # Merom: Core 2 Duo
          :core2
        when 0x78ea4fbc # Penryn
          :penryn
        when 0x6b5a4cd2 # Nehalem
          :nehalem
        when 0x573b5eec # Westmere
          :westmere
        when 0x5490b78c # Sandy Bridge
          :sandybridge
        when 0x1f65e835 # Ivy Bridge
          :ivybridge
        when 0x10b282dc # Haswell
          :haswell
        when 0x582ed09c # Broadwell
          :broadwell
        when 0x37fc219f # Skylake
          :skylake
        when 0x0f817246 # Kaby Lake
          :kabylake
        when 0x38435547 # Ice Lake
          :icelake
        when 0x1cf8a03e # Comet Lake
          :cometlake
        else
          :dunno
        end
      end

      def sysctl_bool!(key)
        sysctl_int(key) == 1
      end

      def sysctl_int(key)
        sysctl_n(key).to_i & 0xffffffff
      end

      def sysctl_n(*keys)
        (@properties ||= {}).fetch(keys) do
          @properties[keys] = Utils.popen_read("/usr/sbin/sysctl", "-n", *keys)
        end
      end
    end
  end
end
